//
//  Property.swift
//  cnswift-demo
//
//  Created by YangWei on 2020/12/29.
//  Copyright © 2020 YangWei. All rights reserved.
//

import UIKit

class Properties: SWDBaseObject {
    
    class func run() {
        
        let properties = Properties()
        
        // 1 存储属性
        properties.storedProperties()
        
        // 1.1 常量结构体实例的存储属性
        properties.storedPropertiesOfConstantStructureInstances()
        
        // 1.2 延迟加载存储属性
        properties.lazyStoredProperties()
        
        // 2 计算属性
        properties.computedProperties()
        
        // 2.1 简化 Setter 声明
        properties.shorthandSetterDeclaration()
        
        // 2.2 简化 Getter 声明
        properties.shorthandGetterDeclaration()
        
        // 2.3 只读计算属性
        properties.readOnlyComputedProperties()
        
        // 3 属性观察器
        properties.propertyObservers()
        
        // 4 属性包装器
        properties.propertyWrappers()
        
        // 4.1 设置被包装属性的初始值
        properties.settingInitialValuesForWrappedProperties()
        
        // 4.2 从属性包装器中呈现一个值
        properties.projectingAValueFromAPropertyWrapper()
        
        // 5 全局变量和局部变量
        properties.globalAndLocalVariables()
        
        // 6.1 类型属性语法
        properties.typePropertySyntax()
        
        // 6.2 获取和设置类型属性的值
        properties.queryingAndSettingTypeProperties()
    }
    
    /// 1 存储属性
    func storedProperties() {
        let title = "1 存储属性";
        printStartLine(title)
        
        var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
        // 该区间表示整数 0，1，2
        print(rangeOfThreeItems)
        
        rangeOfThreeItems.firstValue = 6
        // 该区间现在表示整数 6，7，8
        print(rangeOfThreeItems)
        
        printEndLine(title)
    }
    
    /// 1.1 常量结构体实例的存储属性
    func storedPropertiesOfConstantStructureInstances() {
        let title = "1.1 常量结构体实例的存储属性";
        printStartLine(title)
        
        let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
        // 该区间表示整数 0，1，2，3

//        rangeOfFourItems.firstValue = 6
        // 尽管 firstValue 是个可变属性，但这里还是会报错
        
        printEndLine(title)
    }
    
    /// 1.2 延迟加载存储属性
    func lazyStoredProperties() {
        let title = "1.2 延迟加载存储属性";
        printStartLine(title)
        
        let manager = DataManager()
        manager.data.append("Some data")
        manager.data.append("Some more data")
        // DataImporter 实例的 importer 属性还没有被创建
        
        print(manager.importer.fileName)
        // DataImporter 实例的 importer 属性现在被创建了
        // 输出“data.txt”
        
        printEndLine(title)
    }
    
    /// 2 计算属性
    func computedProperties() {
        let title = "2 计算属性";
        printStartLine(title)
        
        // 初始值原点是 (0, 0)，宽度高度都是 10
        var square = Rect(origin: Point(x: 0.0, y: 0.0),
            size: Size(width: 10.0, height: 10.0))
        //  中心点 (5, 5)
        let initialSquareCenter = square.center
        print("square.origin is now at (\(initialSquareCenter.x), \(initialSquareCenter.y))")
        // square.origin is now at (5.0, 5.0)
        
        // 修改中心点到 (15, 15)，原点也将被调整
        square.center = Point(x: 15.0, y: 15.0)
        print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
        // 打印“square.origin is now at (10.0, 10.0)”
        
        printEndLine(title)
    }
    
    /// 2.1 简化 Setter 声明
    func shorthandSetterDeclaration() {
        let title = "2.1 简化 Setter 声明";
        printStartLine(title)
        
        // 看 AlternativeRect 定义对比和 Rect 差别
        var square = AlternativeRect(origin: Point(x: 0.0, y: 0.0),
            size: Size(width: 10.0, height: 10.0))
        
        let initialSquareCenter = square.center
        print("square.origin is now at (\(initialSquareCenter.x), \(initialSquareCenter.y))")
        
        // 修改中心点到 (15, 15)，原点也将被调整
        square.center = Point(x: 15.0, y: 15.0)
        print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
        
        printEndLine(title)
    }
    
    /// 2.2 简化 Getter 声明
    func shorthandGetterDeclaration() {
        let title = "2.2 简化 Getter 声明";
        printStartLine(title)
        
        // 看 CompactRect 定义对比和 Rect 差别
        var square = CompactRect(origin: Point(x: 0.0, y: 0.0),
            size: Size(width: 10.0, height: 10.0))
        
        let initialSquareCenter = square.center
        print("square.origin is now at (\(initialSquareCenter.x), \(initialSquareCenter.y))")
        
        // 修改中心点到 (15, 15)，原点也将被调整
        square.center = Point(x: 15.0, y: 15.0)
        print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
        
        printEndLine(title)
    }
    
    /// 2.3 只读计算属性
    func readOnlyComputedProperties() {
        let title = "2.3 只读计算属性";
        printStartLine(title)
        
        // 看 Cuboid 定义
        let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
        print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
        // 打印“the volume of fourByFiveByTwo is 40.0”
        
        printEndLine(title)
    }
    
    /// 3 属性观察器
    func propertyObservers() {
        let title = "3 属性观察器";
        printStartLine(title)
        
        // 看 StepCounter 定义
        let stepCounter = StepCounter()
        stepCounter.totalSteps = 200
        // 将 totalSteps 的值设置为 200
        // 增加了 200 步
        stepCounter.totalSteps = 360
        // 将 totalSteps 的值设置为 360
        // 增加了 160 步
        stepCounter.totalSteps = 896
        // 将 totalSteps 的值设置为 896
        // 增加了 536 步
        
        printEndLine(title)
    }
    
    /// 4 属性包装器
    func propertyWrappers() {
        let title = "4 属性包装器";
        printStartLine(title)
        
        var rectangle = SmallRectangle()
        print(rectangle.height)
        // 打印 "0"

        rectangle.height = 10
        print(rectangle.height)
        // 打印 "10"

        rectangle.height = 24
        print(rectangle.height)
        // 打印 "12"
        
        var rectangleV2 = SmallRectangleV2()
        rectangleV2.height = 24
        print(rectangleV2.height)
        // 打印 "12"
        
        printEndLine(title)
    }
    
    /// 4.1 设置被包装属性的初始值
    func settingInitialValuesForWrappedProperties() {
        let title = "4.1 设置被包装属性的初始值";
        printStartLine(title)
        
        // 调用 SmallNumber() 来创建包装 height 和 width 的 SmallNumber 的实例。构造器内部的代码使用默认值 0 和 12 设置初始的被包装值和初始的最大值。
        let zeroRectangle = ZeroRectangle()
        print(zeroRectangle.height, zeroRectangle.width)
        // 打印 "0 0"
        
        let unitRectangle = UnitRectangle()
        print(unitRectangle.height, unitRectangle.width)
        // 打印 "1 1"
        
        var narrowRectangle = NarrowRectangle()
        print(narrowRectangle.height, narrowRectangle.width)
        // 打印 "2 3"
         
        narrowRectangle.height = 100
        narrowRectangle.width = 100
        print(narrowRectangle.height, narrowRectangle.width)
        // 打印 "5 4"
        
        var mixedRectangle = MixedRectangle()
        print(mixedRectangle.height)
        // 打印 "1"
         
        mixedRectangle.height = 20
        print(mixedRectangle.height)
        // 打印 "12"
        
        printEndLine(title)
    }
    
    /// 4.2 从属性包装器中呈现一个值
    func projectingAValueFromAPropertyWrapper() {
        let title = "4.2 从属性包装器中呈现一个值";
        printStartLine(title)
        
        var someStructure = SomeStructure()
        someStructure.someNumber = 4
        // 通过 $ 判断新赋的值是否被属性包装器替换
        print(someStructure.$someNumber)
        // 打印 "false"

        someStructure.someNumber = 55
        print(someStructure.$someNumber)
        // 打印 "true"
        
        printEndLine(title)
    }
    
    func someFunction() {
        // 在局部存储型变量上使用属性包装器
        @SmallNumber var myNumber: Int = 0

        myNumber = 10
        // 这时 myNumber 是 10
        print(myNumber)

        myNumber = 24
        // 这时 myNumber 是 12
        print(myNumber)
    }
    
    /// 5 全局变量和局部变量
    func globalAndLocalVariables() {
        let title = "5 全局变量和局部变量";
        printStartLine(title)
        
        someFunction()
        
        printEndLine(title)
    }
    
    /// 6.1 类型属性语法
    func typePropertySyntax() {
        let title = "6.1 类型属性语法";
        printStartLine(title)
        
        // 查看 SomeClassV2 SomeStructureV2 SomeEnumeration 定义语法
        
        printEndLine(title)
    }
    
    /// 6.2 获取和设置类型属性的值
    func queryingAndSettingTypeProperties() {
        let title = "6.2 获取和设置类型属性的值";
        printStartLine(title)
        
        print(SomeStructure.storedTypeProperty)
        // 打印“Some value.”
        SomeStructure.storedTypeProperty = "Another value."
        print(SomeStructure.storedTypeProperty)
        // 打印“Another value.”
        print(SomeEnumeration.computedTypeProperty)
        // 打印“6”
        print(SomeClassV2.computedTypeProperty)
        // 打印“27”
        
        // 创建两个声道 leftChannel 和 rightChannel，用以表示立体声系统的音量：
        var leftChannel = AudioChannel()
        var rightChannel = AudioChannel()

        // 将左声道 currentLevel 设置成 7，maxInputLevelForAllChannels 也会更新成 7：
        leftChannel.currentLevel = 7
        print(leftChannel.currentLevel)
        // 输出“7”
        print(AudioChannel.maxInputLevelForAllChannels)
        // 输出“7”

        // 将右声道 currentLevel 设置成 11，会被修正到最大值 10，maxInputLevelForAllChannels 的值也会更新到 10
        rightChannel.currentLevel = 11
        print(rightChannel.currentLevel)
        // 输出“10”
        print(AudioChannel.maxInputLevelForAllChannels)
        // 输出“10”
        
        printEndLine(title)
    }
}

/// 1 存储属性
/// 1.1 常量结构体实例的存储属性
/// 定义一个结构体用于描述整数的区间
struct FixedLengthRange {
    var firstValue: Int
    // 这个范围值在被创建后不能被修改
    let length: Int
}

/// 1.2 延迟加载存储属性
/// 数据导入类：使用了延时加载存储属性来避免复杂类中不必要的初始化工作
class DataImporter {
    /*
    DataImporter 是一个负责将外部文件中的数据导入的类。
    这个类的初始化会消耗不少时间。
    */
    var fileName = "data.txt"
    // 这里会提供数据导入功能
}

/// 1.2 延迟加载存储属性
/// 数据管理
class DataManager {
    lazy var importer = DataImporter()
    var data = [String]()
    // 这里会提供数据管理功能
}

/// 2 计算属性
/// 一个 (x, y) 的坐标
struct Point {
    var x = 0.0, y = 0.0
}

/// 2 计算属性
/// 一个 width 和一个 height 的 size
struct Size {
    var width = 0.0, height = 0.0
}

/// 2 计算属性
/// 一个矩形相关的结构体，提供一个中心点计算属性，可以通过设置新的中心点移动矩形
struct Rect {
    var origin = Point()
    var size = Size()
    // 计算属性：中心点可以从原点和大小算出，不需要将中心点以 Point 类型的值来保存
    var center: Point {
        get {
            let centerX = origin.x + (size.width / 2)
            let centerY = origin.y + (size.height / 2)
            return Point(x: centerX, y: centerY)
        }
        set(newCenter) {
            origin.x = newCenter.x - (size.width / 2)
            origin.y = newCenter.y - (size.height / 2)
        }
    }
}

// 2.1 简化 Setter 声明
/// 简化 setter 声明的 Rect 结构体
struct AlternativeRect {
    var origin = Point()
    var size = Size()
    var center: Point {
        get {
            let centerX = origin.x + (size.width / 2)
            let centerY = origin.y + (size.height / 2)
            return Point(x: centerX, y: centerY)
        }
        // 没有设置参数名 对比 set(newCenter) ，使用 newValue
        set {
            origin.x = newValue.x - (size.width / 2)
            origin.y = newValue.y - (size.height / 2)
        }
    }
}

/// 2.2 简化 Getter 声明
/// 同时简化 Getter 声明和 setter 声明
struct CompactRect {
    var origin = Point()
    var size = Size()
    var center: Point {
        get {
            // 此处是单一表达式，可以省略 return
            Point(x: origin.x + (size.width / 2),
                  y: origin.y + (size.height / 2))
        }
        set {
            origin.x = newValue.x - (size.width / 2)
            origin.y = newValue.y - (size.height / 2)
        }
    }
}

/// 2.3 只读计算属性
/// 表示三维空间的立方体，包含 width、height 和 depth 属性。
/// 名为 volume 的只读计算属性用来返回立方体的体积。
/// 为 volume 提供 setter 毫无意义，因为无法确定如何修改 width、height 和 depth 三者的值来匹配新的 volume。
struct Cuboid {
    var width = 0.0, height = 0.0, depth = 0.0
    // 计算体积：只读计算属性总是返回一个值，通过点运算符访问，不能设置新的值
    var volume: Double {
        // 只读计算属性的声明可以去掉 get 关键字和花括号：
        return width * height * depth // return 也可以省略
    }
}

/// 3 属性观察器
/// 统计一个人步行时的总步数（willSet 和 didSet 实际运用例子）
class StepCounter {
    // 存储属性，包含 willSet 和 didSet 观察器。
    var totalSteps: Int = 0 {
        willSet(newTotalSteps) {
            print("将 totalSteps 的值设置为 \(newTotalSteps)")
        }
        // 没有为旧值提供参数名称，默认值 oldValue 表示旧值参数名
        didSet {
            if totalSteps > oldValue  {
                print("增加了 \(totalSteps - oldValue) 步")
            }
        }
    }
}

/// 4 属性包装器
/// TwelveOrLess 结构体确保它包装的值始终小于等于 12。（属性包装器定义示例）
@propertyWrapper
struct TwelveOrLess {
    private var number = 0
    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, 12) }
    }
}

/// 4 属性包装器
/// 存储小矩形的结构体（属性包装器应用示例）
struct SmallRectangle {
    // 通过 TwelveOrLess 属性包装器来确保它的长宽均小于等于 12
    @TwelveOrLess var height: Int
    @TwelveOrLess var width: Int
}

/// 4 属性包装器
/// 不使用特殊特性语法带来的优势，在 getter 和 setter 中自定义写应用属性包装行为的代码
struct SmallRectangleV2 {
    // _height 和 _width 属性存储了一个属性包装的实例， TwelveOrLess
    private var _height = TwelveOrLess()
    private var _width = TwelveOrLess()
    // height 和 width 的 getter 和 setter 包装了 wrappedValue 属性的访问。
    var height: Int {
        get { return _height.wrappedValue }
        set { _height.wrappedValue = newValue }
    }
    var width: Int {
        get { return _width.wrappedValue }
        set { _width.wrappedValue = newValue }
    }
}

/// 4.1 设置被包装属性的初始值
/// SmallNumber是 TwelveOrLess 的扩展版本。定义了能设置被包装值和最大值的构造器
@propertyWrapper
struct SmallNumber {
    private var maximum: Int
    private var number: Int
 
    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, maximum) }
    }
    
    /*
    SmallNumber 的定义包括三个构造器
    init()
    init(wrappedValue:)
    init(wrappedValue:maximum:)
    */
    init() {
        maximum = 12
        number = 0
    }
    init(wrappedValue: Int) {
        maximum = 12
        number = min(wrappedValue, maximum)
    }
    init(wrappedValue: Int, maximum: Int) {
        self.maximum = maximum
        number = min(wrappedValue, maximum)
    }
}

/// 4.1 设置被包装属性的初始值
struct ZeroRectangle {
    // 把包装器应用于属性且没有设定初始值时， 使用 init() 构造器来设置包装器
    @SmallNumber var height: Int
    @SmallNumber var width: Int
}

/// 4.1 设置被包装属性的初始值
struct UnitRectangle {
    // 当为属性指定初始值时，使用 init(wrappedValue:) 构造器来设置包装器
    // 调用 SmallNumber(wrappedValue: 1)来创建包装 height 和 width 的 SmallNumber 的实例。
    @SmallNumber var height: Int = 1
    @SmallNumber var width: Int = 1
}

/// 4.1 设置被包装属性的初始值
struct NarrowRectangle {
    // 提供初始值和最大值，Swift 使用 init(wrappedValue:maximum:) 构造器
    @SmallNumber(wrappedValue: 2, maximum: 5) var height: Int
    @SmallNumber(wrappedValue: 3, maximum: 4) var width: Int
}

/// 4.1 设置被包装属性的初始值
struct MixedRectangle {
    // 包含属性包装器实参，也可以使用赋值来指定初始值
    // 调用 SmallNumber(wrappedValue: 1) 创建包装 height 的 SmallNumber 一个实例，实例使用默认最大值 12。
    @SmallNumber var height: Int = 1
    // 调用 SmallNumber(wrappedValue: 2, maximum: 9) 创建包装 width 的 SmallNumber 一个实例。
    @SmallNumber(maximum: 9) var width: Int = 2
}

/// 4.2 从属性包装器中呈现一个值
/// SmallNumber 结构体添加 projectedValue 属性以追踪属性包装是否在保存新值之前调整了新值的大小
@propertyWrapper
struct SmallNumberV2 {
    private var number: Int
    // 添加 projectedValue 属性以追踪属性包装是否在保存新值之前调整了新值的大小
    private(set) var projectedValue: Bool

    var wrappedValue: Int {
        get { return number }
        set {
            if newValue > 12 {
                number = 12
                projectedValue = true
            } else {
                number = newValue
                projectedValue = false
            }
        }
    }
    
    init() {
        self.number = 0
        self.projectedValue = false
    }
}

/// 4.2 从属性包装器中呈现一个值
/// 6.1 类型属性语法
/// 6.2 获取和设置类型属性的值
struct SomeStructure {
    @SmallNumberV2 var someNumber: Int
    
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 1
    }
}

/// 4.2 从属性包装器中呈现一个值
enum EnumSize {
    case small, large
}

/// 4.2 从属性包装器中呈现一个值
struct SizedRectangle {
    @SmallNumberV2 var height: Int
    @SmallNumberV2 var width: Int

    mutating func resize(to size: EnumSize) -> Bool {
        switch size {
        case .small:
            height = 10
            width = 20
        case .large:
            height = 100
            width = 100
        }
        // 当从类型的一部分代码中访问被呈现值，例如属性 getter 或实例方法，可以在属性名称之前省略 self.，就像访问其他属性一样
        return $height || $width
    }
}

/// 6.1 类型属性语法
/// 6.2 获取和设置类型属性的值
enum SomeEnumeration {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 6
    }
}

/// 6.1 类型属性语法
/// 6.2 获取和设置类型属性的值
class SomeClassV2 {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 27
    }
    class var overrideableComputedTypeProperty: Int {
        return 107
    }
}

/// 6.2 获取和设置类型属性的值：使用 AudioChannel 结构体表示音频声道
struct AudioChannel {
    // 表示音量的最大上限阈值，一个值为 10 的常量，对所有实例可见
    // 如果音量高于 10，则取最大上限值 10
    static let thresholdLevel = 10
    // 表示所有 AudioChannel 实例的最大输入音量，初始值是 0
    static var maxInputLevelForAllChannels = 0
    // 存储型实例属性，表示当前声道现在的音量，取值为 0 到 10。
    var currentLevel: Int = 0 {
        // 属性观察器来检查每次设置后的属性值
        didSet {
            if currentLevel > AudioChannel.thresholdLevel {
                // 将当前音量限制在阈值之内
                currentLevel = AudioChannel.thresholdLevel
            }
            if currentLevel > AudioChannel.maxInputLevelForAllChannels {
                /// 存储当前音量作为新的最大输入音量
                AudioChannel.maxInputLevelForAllChannels = currentLevel
            }
        }
    }
}
